package org.hepeng.workx.exception.translate;

import lombok.Data;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author he peng
 */
public class ExceptionTranslateMethodResolver {

    public static final ReflectionUtils.MethodFilter EXCEPTION_TRANSLATE_METHODS = method ->
            (AnnotationUtils.findAnnotation(method, ExceptionTranslate.class) != null);

    private Map<Method , Set<ExceptionTranslator>> mappedTranslators = new ConcurrentHashMap<>();

    private Map<Class<?> , ExceptionTranslatorReflectInvoker> mappedTranslatorMethods = new ConcurrentHashMap<>();

    public ExceptionTranslateMethodResolver(Class<?> handlerType , ApplicationContext context) {
        for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_TRANSLATE_METHODS)) {
            ExceptionTranslate ann = method.getAnnotation(ExceptionTranslate.class);
            Set<ExceptionTranslator> translators = new HashSet<>();
            for (Class<?> translatorClass : ann.translators()) {

                Map<String, ?> beans = context.getBeansOfType(translatorClass);
                for (Map.Entry<String, ?> entry : beans.entrySet()) {
                    Object translator = context.getBean(entry.getKey(), translatorClass);
                    if (Objects.nonNull(translator)) {
                        translators.add((ExceptionTranslator) translator);
                        MethodIntrospector.selectMethods(translatorClass , (ReflectionUtils.MethodFilter) method1 -> {
                            if (method1.getParameterCount() == 1) {
                                Class<?> parameterType = method1.getParameterTypes()[0];
                                ExceptionTranslatorReflectInvoker reflectInvoker = new ExceptionTranslatorReflectInvoker();
                                reflectInvoker.setMethod(method1);
                                reflectInvoker.setExceptionTranslator((ExceptionTranslator) translator);
                                mappedTranslatorMethods.put(parameterType , reflectInvoker);
                                return true;
                            }
                            return false;
                        });

                        /*Class<?> genericClass = GenericTypeResolver.resolveTypeArgument(translatorClass, ExceptionTranslator.class);
                        Class<?> returnType = GenericTypeResolver.resolveReturnType(method, handlerType);
                        if (Objects.equals(genericClass , returnType)) {
                            translators.add((ExceptionTranslator) translator);

                            MethodIntrospector.selectMethods(translatorClass , (ReflectionUtils.MethodFilter) method1 -> {
                                if (method1.getParameterCount() == 1) {
                                    Class<?> parameterType = method1.getParameterTypes()[0];
                                    ExceptionTranslatorReflectInvoker reflectInvoker = new ExceptionTranslatorReflectInvoker();
                                    reflectInvoker.setMethod(method1);
                                    reflectInvoker.setExceptionTranslator((ExceptionTranslator) translator);
                                    mappedTranslatorMethods.put(parameterType , reflectInvoker);
                                    return true;
                                }
                                return false;
                            });

                        }*/
                    }
                }
            }
            mappedTranslators.put(method , translators);
        }
    }

    public Set<ExceptionTranslator> getTranslators(Method method) {
        return this.mappedTranslators.get(method);
    }

    public boolean hasTranslator() {
        return ! mappedTranslators.isEmpty();
    }

    public boolean hasTranslator(Method method) {
        return CollectionUtils.isNotEmpty(mappedTranslators.get(method));
    }

    public ExceptionTranslatorReflectInvoker getInvoker(Class<?> clazz) {
        return mappedTranslatorMethods.get(clazz);
    }

    @Data
    public class ExceptionTranslatorReflectInvoker {
        private Method method;
        private ExceptionTranslator exceptionTranslator;

        public Object invoke(Object obj) throws Exception {
            method.setAccessible(true);
            return this.method.invoke(exceptionTranslator , obj);
        }
    }
}
